local skynet = require "skynet"
require "skynet.manager"
local websocket = require "http.websocket"
local ProtoCoder = require "core.ProtoCoder"
local Module = require "core.Module"
local Service = require "core.Service"
local Timer = require "core.Timer"
local TimeUtil = require "core.TimeUtil"
local Services = require "global.Services"
local Const = require "enum.Const"

---@class Gate
local M = {}

local connections = {}
local agent

---@type GateMaster
local master
local protocol
-- 心跳检测间隔，默认每15秒检测一次
local heartCheckInterval = skynet.getenvn("heart_check_interval") or 15

local function closeFd(fd, code)
    websocket.close(fd)
    if not connections[fd] then
        return
    end

    local connection = connections[fd]
    connections[fd] = nil
    skynet.send(master, "lua", "close", fd)
    skynet.send(agent, "lua", "close", fd, code)
end

-- 刷新心跳
local function refreshHeart(fd)
    if connections[fd] then
        connections[fd].heartTime = TimeUtil.now()
    end
end

-- connect / handshake / message / ping / pong / close / error
local wsHandler = {}
do
    function wsHandler.connect(fd)
        GLOGF("client connect, fd: %d", fd)
    end

    function wsHandler.handshake(fd, header, url)
        -- GLOGF("client handshake, fd: %d, header: %s, url: %s", fd, table.dump(header), url)
        connections[fd] = {
            fd = fd,
            gate = skynet.self(),
            heartTime = TimeUtil.now(),
            addr = websocket.addrinfo(fd)
        }
    end

    function wsHandler.message(fd, msg, msgType)
        assert(msgType == "binary", "msgType must be binary, but " .. tostring(msgType))
        -- GLOGF("client message, fd: %d, msg: %s", fd, table.dump(msg))
        refreshHeart(fd)

        local protoId, data = ProtoCoder.decode(msg)
        if protoId == false then
            GERRF("decode proto error, message: %s, err: %s", msg, data)
            return
        end

        skynet.send(connections[fd].agent or agent, "lua", fd, protoId, ProtoCoder.cs:name(protoId), data)
    end

    wsHandler.ping = refreshHeart
    wsHandler.pong = refreshHeart

    function wsHandler.close(fd, code, reason)
        GLOGF("client close, fd: %d, %s, %s", fd, code, reason)
        closeFd(fd, Const.CONNECT_CLOSE_CODE.DISCOUNNECT)
    end

    function wsHandler.error(fd)
        GLOGF("client error, fd: %d", fd)
        closeFd(fd, Const.CONNECT_CLOSE_CODE.CONNECT_ERROR)
    end
end

local function send(fds, encoded)
    websocket.write(fds, encoded, "binary")
end

-- 发送消息给客户端
function M.send(fd, protoId, data)
    if connections[fd] then
        send(fd, ProtoCoder.encode(protoId, data))
    end
end

-- 发送给所有连接
function M.sendAll(protoId, data)
    local encoded = ProtoCoder.encode(protoId, data)
    for fd in pairs(connections) do
        -- 发送给登录了的玩家
        send(fd, encoded)
    end
end

-- 广播给指定玩家
function M.broadcast(fds, protoId, data)
    local encoded = ProtoCoder.encode(protoId, data)
    for _, fd in ipairs(fds) do
        if connections[fd] then
            send(fd, encoded)
        end
    end
end

function M.close(fd)
    closeFd(fd, Const.CONNECT_CLOSE_CODE.NORMAL)
end

function M.unforward(fd)
    if connections[fd] then
        connections[fd].agent = nil
    end
end

function M.forward(fd, serviceAddr)
    if connections[fd] then
        connections[fd].agent = serviceAddr
    end
end

local function initProto()
    for _, mod in pairs(Module.allProtoMod()) do
        local ok, protoIds = pcall(require, Module.protoPath(mod, true))
        local protoFile = Module.protobufPath(mod)
        if io.exists(protoFile) then
            ProtoCoder.loadfile(protoFile)
            ProtoCoder.register(protoIds)
        end
    end
end

function M.accept(fd, addr)
    websocket.accept(fd, wsHandler, protocol, addr)
    skynet.send(agent, "lua", "accept", fd, addr)
end

function M.start(conf)
    master = conf.master
    protocol = conf.protocol
    agent = skynet.new_hotservice("service/GateAgent")
    skynet.call(agent, "lua", "start", {
        gate = skynet.self()
    })

    initProto()
end

function M.init()
    skynet.dispatch_lua(M)

    Timer.intervalSec(function()
        local now = TimeUtil.now()
        for fd, connection in pairs(connections) do
            if now - heartCheckInterval > connection.heartTime then
                -- 15s内没有任何请求，断开连接
                closeFd(fd, Const.CONNECT_CLOSE_CODE.HEART_LOSS)
            end
        end
    end, heartCheckInterval)
end

return M
